package com.hup.joystick.view.main;

import com.hup.joystick.model.DeviceConfig;
import com.hup.joystick.model.FunctionConfig;
import com.hup.joystick.model.KeyMapConfig;
import com.hup.joystick.model.MainConfig;
import com.hup.joystick.model.MainConfig.FunctionRecord;
import com.hup.joystick.model.joystick.NativeEvent;
import com.hup.joystick.model.joystick.StandardEvent;
import com.hup.joystick.mvp.MainMvp.MainPresenter;
import com.hup.joystick.service.JoyEventService;
import com.hup.joystick.service.jna.DInputHResultException.HResult;
import com.hup.joystick.service.joystick.AutoConnectListener;
import com.hup.joystick.view.functionSetting.FunctionSettingFrame;
import com.hup.joystick.view.joySetting.JoySettingFrame;
import com.hup.joystick.view.main.FunctionPreviewItem.PreviewEditListener;
import com.hup.joystick.view.quickMenu.QuickMenu;
import com.hup.joystick.view.quickMenu.setting.QuickMenuSettingDialog;
import com.hup.utils.commons.ListUtil;
import com.hup.utils.commons.exception.AppExceptionHandler;
import com.hup.utils.commons.hardware.HRobotJni;
import com.hup.utils.commons.start.DialogHelper;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.commons.start.SpringContext;
import com.hup.utils.commons.start.StartupInfo;
import com.hup.utils.swing.ComponentUtil;
import com.hup.utils.swing.FrameUtil;
import com.hup.utils.swing.FrameUtil.Gravity;
import com.hup.utils.swing.customView.ImageButton;
import com.hup.utils.swing.frame.BaseFrame;
import com.hup.utils.swing.frame.BaseJWindow;
import com.hup.utils.swing.frame.BaseWindow;
import com.hup.utils.swing.frame.HelpDialog;
import com.hup.utils.swing.frame.SingletonWindowHelper;
import com.hup.utils.swing.frame.WindowBoundsAdjustor;
import com.hup.utils.swing.frame.WindowBoundsAdjustor.WindowBoundsAdjustorCallback;
import com.hup.utils.swing.frame.WindowEdgeHideHelper;
import com.hup.utils.swing.frame.WindowEdgeHideHelper.EdgeHideWindow;
import com.hup.utils.swing.model.BaseWindowConfig;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;

import javax.swing.AbstractButton;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author hugan
 * @date 2020/10/15
 */
@Log4j2
public class MainWindow extends BaseJWindow
//public class MainWindow extends BaseFrame
        implements EdgeHideWindow {

    private final MainPresenter presenter;
    private final JoyEventService joyEventService;
    private final ViewItemEditHandler viewItemEditHandler = new ViewItemEditHandler();
    private final DeviceHandler deviceHandler = new DeviceHandler();
    private final MainConfig config;
    private MainForm form;
    private SettingMenu settingMenu;
    @Getter
    private WindowEdgeHideHelper edgeHideHelper;

    public MainWindow() {
        super(StartupInfo.getApplicationChName());
        presenter = SpringContext.getBean(MainPresenter.class);
        joyEventService = SpringContext.getBean(JoyEventService.class);
        config = new MainConfig().loadFromFile();

        initView();
        initListener();
        initConfig();
        refreshFunctionConfig();
        startAutoConnect();
    }

    private void initView() {
        form = new MainForm();
        setContentPane(form.root);

        form.txtLock.setVisible(false);
        form.btnDeviceState.setVisible(false);
        form.btnRefreshDeviceState.setVisible(false);
        form.btnStop.setVisible(false);
        ((FunctionPreviewItem) form.pFunction.getComponent(0)).setPreviewEditListener(viewItemEditHandler);

        setAlwaysOnTop(true);
        LookThemeUtil.initFrameSize(this, 180, 500);
        FrameUtil.setLocation(this, Gravity.MID_R);

        settingMenu = new SettingMenu();
    }

    private void initListener() {
        WindowBoundsAdjustor.bind(new BoundsHandler());
        edgeHideHelper = WindowEdgeHideHelper.bind(this);
        //edgeHideHelper.setGrabSize(30);

        form.btnSetting.setActionListener(e -> settingMenu.show(form.btnSetting, 0, form.btnSetting.getHeight()));
        form.btnHide.setActionListener(e -> switchVisible(false));
        form.btnExit.setActionListener(confirm("退出", () -> {
            dispose();
            presenter.doExit();
        }));
        //下面两个控件已被隐藏
        form.btnStart.addActionListener(e -> runWithCatch("启动", this::startAutoConnect));
        form.btnStop.addActionListener(e -> stopAutoConnect());
        form.btnRefreshDeviceState.setActionListener(e -> runWithCatch("启动", this::startAutoConnect));
    }

    private void initConfig() {
        BaseWindowConfig windowConfig = config.windowConfig;
        if (windowConfig.loc != null) setLocation(windowConfig.loc);
        if (windowConfig.size != null) setSize(windowConfig.size.x, windowConfig.size.y);
    }

    private void refreshFunctionConfig() {
        execute("读取配置", () -> {
            presenter.buildFunctionConfig(config);
            return null;
        }, res -> {
            viewItemEditHandler.updateFunctionPreviewItem();
            ComponentUtil.scrollPaneTo(form.spFunction, true, true);
        });
    }

    private void startAutoConnect() {
        log.debug("");
        switchStartState(true);
        presenter.startAutoConnect(deviceHandler);
    }

    private void stopAutoConnect() {
        log.debug("");
        form.btnStop.setEnabled(false);//防止重复触发
        presenter.stopAutoConnect(true);
        switchStartState(false);
        form.btnStop.setEnabled(true);
    }

    private void switchStartState(boolean started) {
        log.debug("started={}", started);
        form.btnStart.setVisible(!started);
        form.btnStop.setVisible(started);
        form.btnRefreshDeviceState.setVisible(false);//启动和停止后都不显示,只有发生错误后才展示
        if (started) {
            form.btnDeviceState.onScanning();
            form.btnDeviceState.setVisible(true);
        } else {
            form.btnDeviceState.setVisible(false);
        }
    }

    /**
     * 配置页面关闭后触发的功能
     */
    public void onWindowClosing(Class<? extends BaseFrame> clz) {
        log.info("clz={}", clz.getSimpleName());
        if (clz == FunctionSettingFrame.class) {
            refreshFunctionConfig();
        }
        startAutoConnect();
        switchVisible(true);//让主窗口显示一下
    }

    /**
     * 锁定设备后外部调用
     */
    public void onLockDevice(boolean b) {
        form.txtLock.setVisible(b);
    }

    private class DeviceHandler implements AutoConnectListener {
        private DeviceConfig currentDeviceConfig;

        @Override
        public void onScanning() {
            form.btnDeviceState.onScanning();
        }

        @Override
        public void beforeConnect(DeviceConfig deviceConfig) {
            log.info("device={}", deviceConfig.name);
            currentDeviceConfig = deviceConfig;
            form.btnDeviceState.onConnected(deviceConfig.name);
            HRobotJni.showMouse();
        }

        @Override
        public KeyMapConfig getKeyMapConfig() {
            return currentDeviceConfig.keyMap;
        }

        @Override
        public void onJoyEvent(NativeEvent none, StandardEvent e) {
            try {
                //if (config.lstFunction.isEmpty()) return;//没有功能时,不影响设备连接状态
                joyEventService.handleJoyEvent(e, config.lstFunction);
            } catch (Throwable err) {
                //代码异常,不影响设备连接状态
                form.btnDeviceState.onErr(err);
            }
        }

        @Override
        public void onException(Throwable e) {
            AppExceptionHandler.handle(e);
            //不可忽略的异常,详看方法注释,需要显示[刷新按钮]
            form.btnDeviceState.onErr(e);
            //不调用switchStartState,直接修改控件
            form.btnRefreshDeviceState.setVisible(true);
            form.btnStart.setVisible(true);
            form.btnStop.setVisible(false);

            StandardEvent event = new StandardEvent(HResult.E_FAIL.val);
            joyEventService.handleJoyEvent(event, config.lstFunction);
        }
    }

    private class BoundsHandler implements WindowBoundsAdjustorCallback {

        @SuppressWarnings("RedundantIfStatement")
        @Override
        public boolean canDrag(AWTEvent e) {
            Component c = (Component) e.getSource();
            if (!c.isEnabled()) return true;
            if (c instanceof JScrollBar
                    || c instanceof AbstractButton
                    || c instanceof ImageButton
            ) {
                return false;
            }
            return true;
        }

        @Override
        public Window getWindow() {
            return MainWindow.this;
        }

        @Override
        public void afterMoved(int x, int y, Boolean onTop, Boolean onLeft) {
            if (config.windowConfig.loc == null) config.windowConfig.loc = new Point();
            config.windowConfig.loc.x = x;
            config.windowConfig.loc.y = y;
            config.save();
        }

        @Override
        public void afterResized(int x, int y, int width, int height) {
            if (config.windowConfig.loc == null) config.windowConfig.loc = new Point();
            config.windowConfig.loc.x = x;
            config.windowConfig.loc.y = y;
            if (config.windowConfig.size == null) config.windowConfig.size = new Point();
            config.windowConfig.size.x = width;
            config.windowConfig.size.y = height;
            config.save();
        }
    }

    private class SettingMenu extends JPopupMenu implements ActionListener {
        private static final String CMD_HELP = "帮助";

        private SettingMenu() {
            add(JoySettingFrame.TITLE).addActionListener(this);
            add(FunctionSettingFrame.TITLE).addActionListener(this);
            add(QuickMenu.TITLE).addActionListener(this);
            add(QuickMenuSettingDialog.TITLE).addActionListener(this);
            add(CMD_HELP).addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            switch (e.getActionCommand()) {
                case JoySettingFrame.TITLE:
                    if (beforeShowFrame(FunctionSettingFrame.class)) JoySettingFrame.showFrame(null);
                    break;
                case FunctionSettingFrame.TITLE:
                    if (beforeShowFrame(JoySettingFrame.class)) FunctionSettingFrame.showFrame(null);
                    break;
                case QuickMenu.TITLE:
                    notifyMouseExited();
                    QuickMenu.showFrame(null, null);
                    break;
                case QuickMenuSettingDialog.TITLE:
                    QuickMenuSettingDialog.showFrame();
                    break;
                case CMD_HELP:
                    HelpDialog.builder().doShow(MainWindow.this, "MainWindow");
                    break;
            }
        }

        /**
         * @return 能否继续打开窗口
         */
        private boolean beforeShowFrame(Class<? extends BaseWindow> clz) {
            BaseWindow window = SingletonWindowHelper.getWindow(clz);
            if (window != null) {
                DialogHelper.confirm("请先关闭:" + window.getBaseTitle()).show((Window) window);
                return false;
            } else {
                stopAutoConnect();
                return true;
            }
        }
    }

    private class ViewItemEditHandler implements PreviewEditListener {

        private void updateFunctionPreviewItem() {
            log.debug("");
            //移除多余的项目
            int functionSize = config.lstFunction.size();
            for (int i = form.pFunction.getComponentCount() - 1; i >= functionSize; i--) form.pFunction.remove(i);
            for (int i = 0; i < functionSize; i++) initPreviewItem(i, config.lstFunction.get(i));
            ComponentUtil.afterChangedChild(form.pFunction);
            joyEventService.notifyConfigChanged(config.lstFunction);
        }

        private void initPreviewItem(int index, FunctionConfig function) {
            int count = form.pFunction.getComponentCount();
            FunctionPreviewItem item;
            if (index > count - 1) {
                item = new FunctionPreviewItem();
                item.setPreviewEditListener(viewItemEditHandler);
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridx = 0;
                gbc.gridy = index;
                gbc.weightx = 1.0;
                gbc.fill = GridBagConstraints.BOTH;
                form.pFunction.add(item, gbc);
            } else {
                item = (FunctionPreviewItem) form.pFunction.getComponent(index);
            }
            item.setFunction(function);
        }

        @Override
        public void onEditPreviewItem(String functionName, boolean enable) {
            doInBackground("保存配置", () -> {
                FunctionRecord functionRecord = ListUtil.get(config.lstFunctionRecord, record -> record.name.equals(functionName));
                if (functionRecord != null) {
                    functionRecord.setEnable(enable);
                    config.save();
                }
                joyEventService.notifyConfigChanged(config.lstFunction);
            });
        }

        @Override
        public void onMove(FunctionPreviewItem item, boolean isUp) {
            log.debug("isUp={}, {}", isUp, item);
            int index = config.lstFunction.indexOf(item.getFunction());
            int targetIndex;
            if (isUp && index > 0) {
                targetIndex = index - 1;
            } else if (!isUp && index < config.lstFunction.size() - 1) {
                targetIndex = index + 1;
            } else {
                return;
            }
            ListUtil.swap(config.lstFunction, index, targetIndex);
            ListUtil.swap(config.lstFunctionRecord, index, targetIndex);
            updateFunctionPreviewItem();
            ComponentUtil.scrollPaneTo(form.spFunction, form.pFunction.getComponent(targetIndex));
            config.save();
        }

        @Override
        public void onShowFunctionSettingFrame(FunctionPreviewItem item) {
            //log.info("编辑={}", item.getFunction().getName());
            if (settingMenu.beforeShowFrame(JoySettingFrame.class))
                FunctionSettingFrame.showFrame(item);
        }
    }

}
